package greensimulation;

import com.sun.javafx.geom.Line2D;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeMap;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Grid extends Parent {
    
    private static final Duration DURATION500 = Duration.valueOf(500);
    private static final Duration DURATION1000 = Duration.valueOf(1000);
    private static final Interpolator INTERPOLATOR = Interpolator.EASE_BOTH;
    private static final double SPACING_HOR = 200;
    private static final double SPACING_VER = 150;
    private static final int HOR_COMPUTERS = 5;
    public static final double X_OFFSET = 15;
    private double Y_OFFSET = 15;
    private double Y_OFFSET_DIFF = 0;
    private Computer[] computers;
    private Computer[][] grid;
    private Group group_computers = new Group();
    private Group group_garbage = new Group();
    private Group group_line = new Group();
    private Rectangle line_separation;
    private Timeline timeline;
    private Grid garbageGrid = null;
    private Stage stage;
    private boolean firstGarbageCreation = true;
    
    public Grid(Computer[] computers, Stage stage, double Y_OFFSET){
        this.Y_OFFSET += Y_OFFSET;
        this.Y_OFFSET_DIFF = Y_OFFSET;
        this.computers = computers.clone();
        this.stage = stage;
        generateGrid();
    }    
    
    public Grid(int nComp, Stage stage){
        computers = new Computer[nComp];
        for (int i = 0; i < nComp; i++)
            computers[i] = new Computer();
        this.stage = stage;
        updateMinMaxGreenRatio();
        for (int i = 0; i < nComp; i++)
            computers[i].setOpacity(0);
        generateGrid();
    }    

    public void generateGrid(){
        getChildren().addAll(group_computers, group_garbage, group_line);

        updateGridPosition();
    }
    
    public double getGridWidth(){
        return X_OFFSET * 2 + HOR_COMPUTERS * SPACING_HOR;
    }
    
    public double getGridHeight(){
        return (Y_OFFSET-Y_OFFSET_DIFF) * 2 + grid.length * SPACING_VER;
    }
    
    private void updateMinMaxGreenRatio(){
        double minG = 1, maxG = 0;
        double minP = 1, maxP = 0;
        for(Computer tmp : computers){
            double Gratio = tmp.getGreenRatio();
            minG = Math.min(minG, Gratio);
            maxG = Math.max(maxG, Gratio);
            double Pratio = tmp.getPerfRatio();
            minP = Math.min(minP, Pratio);
            maxP = Math.max(maxP, Pratio);
        }
        for(Computer tmp : computers)
            tmp.setMinMaxGreenPerfRatio(minG, maxG, minP, maxP);
    }
    
    private void updateGridPosition(){
        group_computers.getChildren().clear();
        
        int nComp = computers.length;
        int ver_computers = (nComp % HOR_COMPUTERS == 0) ? nComp / HOR_COMPUTERS : nComp / HOR_COMPUTERS + 1;
        grid = new Computer[ver_computers][HOR_COMPUTERS];
        for(int i=0 ; i<nComp ; i++){
            grid[i / HOR_COMPUTERS][i % HOR_COMPUTERS] = computers[i];
            group_computers.getChildren().addAll(computers[i]);
        }
            
        timeline = new Timeline();
        final ObservableList<KeyFrame> keyFrames = timeline.getKeyFrames();
        
        for(int i=0 ; i<grid.length ; i++)
            for(int j=0 ; j<grid[i].length ; j++){
                Computer comp = grid[i][j];
                if(comp != null){
                    keyFrames.addAll(
                            new KeyFrame(DURATION1000, new KeyValue(comp.opacityProperty(), 1, INTERPOLATOR)),
                            new KeyFrame(DURATION1000, new KeyValue(comp.translateXProperty(), X_OFFSET+j*SPACING_HOR, INTERPOLATOR)),
                            new KeyFrame(DURATION1000, new KeyValue(comp.translateYProperty(), Y_OFFSET+i*SPACING_VER, INTERPOLATOR))
                    );
                }
            }
        
        timeline.play();
    }
    
    public void updateGreenRatio(double greenRatio){
        int totalComp = computers.length + ((garbageGrid==null) ? 0 : garbageGrid.computers.length);
        int newNComp = (int)(greenRatio * totalComp);
        List<Computer[]> separated = separateComputers(totalComp - newNComp, newNComp);
        computers = separated.get(1);

        updateGridPosition();

        double width = getGridWidth();
        double heidth = getGridHeight();
        double offset_diff = heidth+5;

        garbageGrid = new Grid(separated.get(0), stage, offset_diff);
        //garbageGrid.setTranslateY(heidth+30);
        group_garbage.getChildren().clear();
        group_garbage.getChildren().add(garbageGrid);

        Timeline tl = new Timeline();
        final ObservableList<KeyFrame> keyFrames = tl.getKeyFrames();
        
        if(firstGarbageCreation){
            line_separation = new Rectangle(0f, (float)heidth, (float)width,
                    garbageGrid.getGridHeight()+10);

            Stop[] stops = new Stop[]{
                new Stop(0, Color.WHITE),
                new Stop(0.5, Color.BLACK),
                new Stop(1, Color.WHITE)};
            LinearGradient lg = new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, stops);
            line_separation.setFill(lg);
            line_separation.setOpacity(0);

            keyFrames.addAll(
                    new KeyFrame(DURATION1000, new KeyValue(line_separation.opacityProperty(), 0.8, INTERPOLATOR))
                    );
            group_line.getChildren().addAll(line_separation);
        }
        else {
            keyFrames.addAll(
                    new KeyFrame(DURATION1000, new KeyValue(line_separation.heightProperty(), 
                        garbageGrid.getGridHeight()+10, INTERPOLATOR)),
                    new KeyFrame(DURATION1000, new KeyValue(line_separation.yProperty(),
                        heidth, INTERPOLATOR))
                    );
        }

        double stageFinalHeight = 60+getGridHeight()+garbageGrid.getGridHeight();

        keyFrames.addAll(
                new KeyFrame(DURATION1000, new KeyValue(stage.heightProperty(), stageFinalHeight, INTERPOLATOR))
        );
        tl.play();

        firstGarbageCreation = false;
    }
    
    private List<Computer[]> separateComputers(int nGarbage, int n){
        Computer[] garb = new Computer[nGarbage];
        Computer[] keep = new Computer[n];
        TreeMap<Double, Computer> tm = new TreeMap<Double, Computer>();
        for(Computer tmp : computers)
            tm.put(tmp.getGreenRatio()+Math.random()*0.0001, tmp);
        if(garbageGrid != null)
            for(Computer tmp : garbageGrid.computers)
                tm.put(tmp.getGreenRatio()+Math.random()*0.0001, tmp);

        Iterator<Double> ns = tm.descendingKeySet().iterator();
        for(int i=0 ; i<nGarbage ; i++)
            garb[nGarbage-1-i] = tm.get(ns.next());
        for(int i=0 ; i<n ; i++)
            keep[n-i-1] = tm.get(ns.next());
        List<Computer[]> f = new ArrayList<Computer[]>();
        f.add(garb);
        f.add(keep);
        return f;
    }
}